// TODO: CODE REVIEW & CLEANUP!
//
// ClientControlSupport.cs
//
// Implements the HomeUX control framework, which can be used to implement controls that support
// touch and/or ISyncClient but that are not necessarily tied to the HomeUX client application
// framework (i.e. types in the HomeUX.ClientAppSupport namespace).
//

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Xml;
using HomeUX.Synchronization;
using HomeUX.Utilities;
using SyncPathClass = HomeUX.Synchronization.SyncPath;
using System.ComponentModel;
using HomeUX.ClientAppSupport;
using HomeUX.Controls;

namespace HomeUX.ClientControlSupport
{

/// <summary>
/// Application-wide services used by controls.  For applications based on <r>ClientFramework</r>,
/// these services are implemented by <r>ClientFramework</r>.  However, alternate implementations
/// are possible, and <r>ClientGlobals</r> exists so that controls don't need to hard-code a
/// dependency onto <r>ClientFramework</r> (or <r>SyncClient</r>).
/// </summary>
///
public static class ClientGlobals
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Properties
    //

    /// <summary>
    /// Attempts to change the application's current screen to a given screen.  The parameter is
    /// the name of the screen to navigate to.
    /// </summary>
    /// 
    /// <remarks>
    /// This method is primarily for use by controls that don't want to hard-code a dependency
    /// onto <r>ClientFramework</r>.  Within applications that do leverage <r>ClientFramework</r>,
    /// it's generally better to use <c>ClientFramework.Instance.</c><s>ClientFramework.GoToScreen</s>
    /// instead of this method, since <r>ClientFramework.GoToScreen</r> includes overloads that
    /// use types instead of strings, thus reducing the chance of errors that can only be caught
    /// at run time.
    /// </remarks>
    ///
    public static Func<string,bool> TryGoToScreen { get; set; }

    // see Notes.txt
    public static Func<FrameworkElement, IDialogSite> DialogSiteProvider { get; set; }

    // see Notes.txt
    public static Func<FrameworkElement, ISyncClient> SyncClientProvider { get; set; }

	/// <summary>
	/// Gets or sets the application-wide touch mode setting.
	/// </summary>
	///
	/// <remarks>
	/// <para>
	/// Typically, touch mode is used (i.e. <r>IsTouchMode</r> is set to <n>true</n>) when the
	/// application is run in "kiosk mode", with no hardware keyboard available.  Touch mode is
	/// usually turned off if the application is run within a regular web page on a computer with
	/// a normal keyboard.
	/// </para>
	/// <para>
	/// In touch mode, many touch-screen drivers operate as follows: when you touch the screen,
	/// the mouse cursor moves to the touched location, and mouse-down and mouse-up events are
	/// executed.  Then the cursor stays at that location.  Since you don't want an arrow to
	/// appear on the screen over the last place you touched, the cursor is typically hidden.
	/// But, for the same reason, there's no point displaying a mouse-over effect, either -- that
	/// would simply have the odd effect of highlighting the last button you pressed.
	/// </para>
	/// <para>
	/// Similarly, there's no point displaying which control has keyboard focus, since you don't
	/// have a keyboard.  As with mouse-over visuals, displaying focus would simply shows which
	/// control you last touched -- what's the point of that?
	/// </para>
	/// <para>
	/// So, for those two reasons, in touch mode, the display of both mouse-over and keyboard
	/// focus is suppressed.
	/// </para>
	/// <para>
	/// A special case applies to text boxes.  In touch mode, a software keyboard is available to
	/// let you type text, and the text box UI needs to show which text box you're entering text
	/// box into (in case there are multiple text boxes on the screen) and a caret displays where
	/// the next character will go.  The <r>TouchKeyboard</r> control performs these functions.
	/// <r>TouchKeyboard</r> also has a Tab key, but unlike a hardware keyboard's Tab key, the
	/// software keyboard navigates only among text boxes.
	/// </para>
    /// </remarks>
	///
    public static bool IsTouchMode { get; set; }

	/// <summary>
	/// Gets or sets the application-wide auto-repeat delay time used in touch-enabled user
	/// interface elements such as <r>TouchButton</r>.  This delay time is the time from when the
	/// user first touches the user interface element to the time of the first auto-repeat.
	/// </summary>
	///
	/// <remarks>
	/// This property is only used for user interface elements that have an auto-repeat feature,
	/// and where that feature is enabled.  For example, see
	/// <r>TouchButton.IsAutoRepeatEnabled</r>.
	/// </remarks>
	///
	/// <seealso cref="AutoRepeatInterval" />
	///
    public static TimeSpan AutoRepeatDelay { get; set; }

	/// <summary>
	/// Gets or sets the application-wide auto-repeat interval time used in touch-enabled user
	/// interface elements such as <r>TouchButton</r>.  This interval is the time between
	/// successive auto-repeats.
	/// </summary>
	///
	/// <remarks>
	/// This property is only used for user interface elements that have an auto-repeat feature,
	/// and where that feature is enabled.  For example, see
    /// <r>TouchButton.IsAutoRepeatEnabled</r>.
	/// </remarks>
	///
	/// <seealso cref="AutoRepeatDelay" />
	///
    public static TimeSpan AutoRepeatInterval { get; set; }

	/// <summary>
	/// Gets or sets the application-wide brush used as a default background color for various
	/// touch-enabled controls, such as the popup of <r>TouchLevel</r>.
	/// </summary>
	///
    public static Brush DefaultBackground { get; set; }

    /// <summary>
    /// TODO - specifies an IAlertsDisplay implementation that's used by Alert 
    /// </summary>
    public static IAlertsDisplay AlertsDisplay { get; set; }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    // see Notes.txt
    public static IDialogSite GetDialogSite(FrameworkElement context)
    {
        if (DialogSiteProvider == null)
            throw new InvalidOperationException("ClientGlobals.DialogSiteProvider is null");
        return DialogSiteProvider(context);
    }

    static NullSyncClient _nullSyncClient;
    // see Notes.txt
    public static ISyncClient GetSyncClient(FrameworkElement context)
    {
        // if SyncClientProvider hasn't been set yet, default it to one that always returns NullSyncClient
        if (SyncClientProvider == null)
        {
            SyncClientProvider = delegate(FrameworkElement element)
            {
                if (_nullSyncClient == null)
                    _nullSyncClient = new NullSyncClient();
                return _nullSyncClient; 
            };
            /*
            // in design mode, we'll get parser errors if there isn't a default ISyncClient
            // provider
            HomeUX.Synchronization.Client.SyncClient.SetAsGlobalProvider();
#warning need to remove the dependency on HomeUX.Synchronization.Client
             */
        }
        return SyncClientProvider(context);
    }

    /// <summary>
    /// If <r>AlertsDisplay</r> has been set to a method that can display an alert message to the
    /// user, that method is called, and <n>true</n> is returned.  Otherwise, <n>false</n> is
    /// returned.
    /// </summary>
    ///
    /// <param name="alertType">The type of alert to display.</param>
    ///
    /// <param name="format">A formatting string for the alert message.</param>
    ///
    /// <param name="args">Formatting arguments.  If no formatting arguments are supplied,
    ///     <pr>format</pr> is displayed as-is, without formatting.</param>
    ///
    public static bool Alert(AlertType alertType, string format, params object[] args)
    {
        if (AlertsDisplay != null)
        {
            AlertsDisplay.AddMessage(alertType, Util.Format(format, args));
            return true;
        }
        else
            return false;
    }
}

public class NullSyncClient : ISyncClient
{
    SyncNode _syncTree;

    public NullSyncClient()
    {
        _syncTree = SyncNode.CreateTree(this);
    }

    public SyncNodeBase SyncTree
    {
        get
        {
            return _syncTree;
        }
    }

    public bool IsOnline
    {
        get
        {
            return true;
        }
    }

    public event IsOnlineChangedEventHandler IsOnlineChanged
    {
        // we never fire this event
        add
        {
        }
        remove
        {
        }
    }

    public void QueueSyncNodeToSendToServer(SyncNodeBase node)
    {
        throw new NotImplementedException();
    }

    public void QueueSyncNodeCommandToSendToServer(SyncNodeBase node, string verb)
    {
        throw new NotImplementedException();
    }

    public void QueueDescendantChangedEvent(SyncNodeBase syncNode, DescendantChanges changes, object originator)
    {
        throw new NotImplementedException();
    }
}

/// <summary>
/// TODO
/// </summary>
///
public interface IDialogSite
{
    /// <summary>
    /// TODO 
    /// </summary>
    /// 
    /// <param name="dialog">TODO</param>
    /// 
    /// <param name="settings">TODO</param>
    ///
    void ShowDialog(FrameworkElement dialog, DialogSettings settings);

    /// <summary>
    /// TODO
    /// </summary>
    ///
    /// <param name="dialog">TODO</param>
    ///
    void RemoveGlow(FrameworkElement dialog);

    /// <summary>
    /// TODO
    /// </summary>
    ///
    /// <param name="dialog">TODO</param>
    ///
    void RepositionDialog(FrameworkElement dialog);
    
    /// <summary>
    /// TODO
    /// </summary>
    /// 
    /// <param name="dialog">TODO</param>
    ///
    /// <param name="restorePreviousFocus">TODO</param>
    ///
    void CloseDialog(FrameworkElement dialog, bool restorePreviousFocus);
}

public interface INotifyDialog
{
    void OnDialogPosition(bool isAbovePeekThroughControl);
    void OnDialogClose();
}

/// <summary>
/// TODO
/// </summary>
///
public class DialogSettings
{
    /// <summary>
    /// If not <n>null</n>, there will be a hole in the translucent "dialog shield" so that this
    /// control will be visible, and the dialog will be positioned immediately below this
    /// control (or above it, depending on available space).  Also, in this case, the dialog
    /// will have a white border, and there will be a "glow" surrounding both the dialog and the
    /// peek-through control, to draw the user's attention there.  If <r>PeekThrough</r> is
    /// <n>null</n>, the dialog is displayed with no additional border, centered over the "dialog
    /// shield".
    /// </summary>
    public Control PeekThrough { get; set; }

    public Action OnClose { get; set; }

    /// <summary>
    /// If true, clicks on "shield" are ignored, as is the Escape key.
    /// </summary>
    public bool IsModal;

    /// <summary>
    /// If nonzero, the dialog is given a background (using the default application background
    /// color) that's this much larger on on all four sides than the dialog.  Note that if
    /// <r>PeekThrough</r> is not <n>null</n> a background is always used, and in this case a
    /// default value is used for the background margin if <r>BackgroundMargin</r> is zero.
    /// </summary>
    public int BackgroundMargin;
}

public static class ClientUtil
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Dialogs
    //

    public static void ShowDialog(FrameworkElement context, FrameworkElement dialog, DialogSettings settings)
    {
        IDialogSite dialogSite = ClientGlobals.GetDialogSite(context);
        if (dialogSite != null)
            dialogSite.ShowDialog(dialog, settings);
    }

    public static void RemoveGlowAroundDialog(FrameworkElement dialog)
    {
        IDialogSite dialogSite = ClientGlobals.GetDialogSite(dialog);
        if (dialogSite != null)
            dialogSite.RemoveGlow(dialog);
    }

    public static void RepositionDialog(FrameworkElement dialog)
    {
        IDialogSite dialogSite = ClientGlobals.GetDialogSite(dialog);
        if (dialogSite != null)
            dialogSite.RepositionDialog(dialog);
    }

    public static void CloseDialog(FrameworkElement dialog, bool restorePreviousFocus)
    {
        IDialogSite dialogSite = ClientGlobals.GetDialogSite(dialog);
        if (dialogSite != null)
            dialogSite.CloseDialog(dialog, restorePreviousFocus);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Miscellaneous
    //
    
    public static string GetLabel(FrameworkElement element)
    {
        if (element == null)
            return "<null>";

        return String.Format("{0} \"{1}\"", element.GetType().Name,
            String.IsNullOrEmpty(element.Name) ? "<unnamed>" : element.Name);
    }

    public static Visibility GetVisibility(bool isVisible)
    {
        return isVisible ? Visibility.Visible : Visibility.Collapsed;
    }

    public static void InterpretUiVisibility(UiVisibility visibility, bool disabled,
        out bool collapse, out bool hide)
    {
        switch (visibility)
        {
        case UiVisibility.CollapseAlways:
            collapse = true;
            hide = true;
            break;
        case UiVisibility.CollapseWhenDisabled:
            collapse = disabled;
            hide = disabled;
            break;
        case UiVisibility.HideWhenDisabled:
            collapse = false;
            hide = disabled;
            break;
        case UiVisibility.VisibleAlways:
            collapse = false;
            hide = false;
            break;
        default:
            throw new InvalidOperationException("Unknown UiVisiblity state");
        }
    }

    public static void ForEachDescendant(Panel parent, Action<UIElement> action)
    {
        foreach (UIElement child in parent.Children)
        {
            var childPanel = child as Panel;
            if (childPanel != null)
                ForEachDescendant(childPanel, action);
            else
                action(child);
        }
    }

    public static void InvokeOnUiThread(int milliseconds, Action action)
    {
        var helper = new InvokeHelper()
        {
            Action = action,
            Timer = new DispatcherTimer()
            {
                Interval = TimeSpan.FromMilliseconds(milliseconds)
            }
        };
        helper.Timer.Tick += helper.Tick;
        helper.Timer.Start();
    }

    public static void StopAllStoryboards(params VisualStateGroup[] visualStateGroups)
    {
        foreach (VisualStateGroup visualStateGroup in visualStateGroups)
        {
            foreach (VisualState visualState in visualStateGroup.States)
                visualState.Storyboard.Stop();
        }
    }

    // generates a polygon that traces the outer edge of a group of adjacent or intersecting
    // rectangles
    // note: this generates points that are clockwise from the highest part of the left edge of
    // the leftmost rectangle; incorrect results happen unless the rectangles are adjacent or
    // intersect, e.g.  if there is an "island" of one or more adjacent or intersecting rectangles
    // that isn't adjacent or intersecting with another "island", then the resulting polygon will
    // encompass only one of the islands
    // side effect: vertices in <rects> are rounded -- you can use this info to tell you more
    // precisely where the points are
    public static PointCollection RectsToPolygonPoints(IList<Rect> rects)
    {
        // since I'll be comparing vertices, reduce round-off errors in <rects>
        for (int i = 0; i < rects.Count; i++)
        {
            Rect rect = rects[i];
            rects[i] = new Rect(Math.Round(rect.Left, 5), Math.Round(rect.Top, 5),
                Math.Round(rect.Width, 5), Math.Round(rect.Height, 5));
        }

        // set <xValues> to a list of x-coordinates used by <rects> (i.e. left and right edges of
        // rectangles), in ascending order, and set <xMap> to map an x-coordinate to its index
        // within <xValues>; do the same for <yValues> and <yMap>
        var xMap = new Dictionary<double, int>();
        var yMap = new Dictionary<double, int>();
        foreach (Rect rect in rects)
        {
            xMap[rect.Left] = 0;
            xMap[rect.Right] = 0;
            yMap[rect.Top] = 0;
            yMap[rect.Bottom] = 0;
        }
        var xValues = new List<double>(xMap.Keys);
        xValues.Sort();
        for (int i = 0; i < xValues.Count; i++)
            xMap[xValues[i]] = i;
        var yValues = new List<double>(yMap.Keys);
        yValues.Sort();
        for (int i = 0; i < yValues.Count; i++)
            yMap[yValues[i]] = i;

        // if the resulting polygon would be zero-width or zero-height, return zero points
        if ((xValues.Count < 2) || (yValues.Count < 2))
            return new PointCollection();

        // In this example, <rects> contains three intersecting/adjacent rectangles: (3,1,9,5),
        // (6,3,11,4), and (6,7,6,2).  <xValues>, <xMap>, <yValues>, and <yMap> are as follows:
        //
        //           11111111
        // 012345678901234567    xValues xMap      yValues yMap
        // 1  +--------+         ------- -------   ------- -------
        // 2  |        |           3     3  -> 0     1     1 -> 0
        // 3  |  +----------+      6     6  -> 1     3     3 -> 1
        // 4  |  |     |    |      12    12 -> 2     6     6 -> 2
        // 5  |  |     |    |      17    17 -> 3     7     7 -> 3
        // 6  +--|-----+    |                        9     9 -> 4
        // 7     +-----+----+
        // 8     |     |
        // 9     +-----+
        //
        // The goal of the next phase is to create a two-dimensional grid that represents <rects>
        // in "x/y index space" instead "x/y coordinate space".  In this example, <xyMap> is:
        //
        //   0123  For example, xyMap[2,1] is true because the area in the x/y coordinate space
        // 0 TTFF  above between (xValues[2],yValues[1]) and (xValues[2+1],yValues[1+1]), i.e.
        // 1 TTTF  the space in the rectangle bounded by (12,3) and (17,6) is covered by one or
        // 2 FTTF  more rectangles in <rects>.
        // 3 FTFF
        // 4 FFFF
        //
        var xyMap = new bool[xMap.Count, yMap.Count];
        int xIndex, yIndex;
        foreach (Rect rect in rects)
        {
            for (yIndex = yMap[rect.Top]; yValues[yIndex] < rect.Bottom; yIndex++)
            {
                for (xIndex = xMap[rect.Left]; xValues[xIndex] < rect.Right; xIndex++)
                    xyMap[xIndex, yIndex] = true;
            }
        }

        /*
        System.Text.StringBuilder sb = new StringBuilder();
        for (yIndex = 0; yIndex < yValues.Count; yIndex++)
        {
            for (xIndex = 0; xIndex < xValues.Count; xIndex++)
                sb.Append(xyMap[xIndex, yIndex] ? 'T' : 'F');
            sb.Append('\n');
        }
        */

        // set (xIndex,yIndex) and (xStartIndex,yStartIndex) to the highest part of the left edge
        // of the leftmost rectangle, in "x/y index space"; in the example above, this would be
        // (0,0), but if the y-positions of the two large rectangles were swapped then this would
        // be (0,1) -- note that <xIndex> is always zero due to the nature of "x/y index space"
        xIndex = 0;
        for (yIndex = 0; yIndex < yValues.Count; yIndex++)
        {
            if (xyMap[xIndex, yIndex])
                break;
        }
        int xStartIndex = xIndex, yStartIndex = yIndex;

        // now, starting at (xIndex,yIndex), I walk clockwise around the edge of the polygon, and
        // set <vertices> to the vertices (in regular "x/y coordinate space") I encounter; in the
        // example above, <vertices> would contain: (3,1), (12,1), (12,3), (17,3), (17,7), (12,7),
        // (12,9), (6,9), (6,6), (3,6), (3,1)
        var vertices = new PointCollection();
        bool stop = false;
        while (true)
        {
            // set <point> to the "x/y coordinate space" equivalent of (xIndex,yIndex)
            Point point = new Point(xValues[xIndex], yValues[yIndex]);

            // add <point> to <vertices>, but if three vertices line up then I collapse them into
            // two; e.g. (3,0), (6,0), and (12,0) are in a horizontal line so when I encounter
            // (12,0) I'd overwrite (6,0), leaving two vertices: (3,0) and (12,0)
            if (vertices.Count > 1)
            {
                Point beforeLast = vertices[vertices.Count - 2];
                Point last = vertices[vertices.Count - 1];
                if (((beforeLast.X == last.X) && (last.X == point.X)) ||
                    ((beforeLast.Y == last.Y) && (last.Y == point.Y)))
                {
                    vertices[vertices.Count - 1] = point;
                }
                else
                    vertices.Add(point);
            }
            else
                vertices.Add(point);

            // if I've reached the starting position, quit
            if (stop)
                break;

            // set <nw>, <ne>, <sw>, and <se> to indicate whether the rectangular area to the
            // northwest (i.e. above and to the left), northeast (above and to the right),
            // southwest (below and to the left), and southest (below and to the right) of
            // (xIndex,yIndex)
            bool nw = (xIndex > 0) && (yIndex > 0) && xyMap[xIndex - 1, yIndex - 1];
            bool ne = (xIndex < xValues.Count) && (yIndex > 0) && xyMap[xIndex, yIndex - 1];
            bool sw = (xIndex > 0) && (yIndex < yValues.Count) && xyMap[xIndex - 1, yIndex];
            bool se = (xIndex < xValues.Count) && (yIndex < yValues.Count) && xyMap[xIndex, yIndex];

            // based on the combination of <nw>, <ne>, <sw>, and <se>, we'll advance
            // (xIndex,yIndex) one unit to the left (west), right (east), up (north), or down
            // (south)
            int pattern = (nw ? 8 : 0) | (ne ? 4 : 0) | (sw ? 2 : 0) | (se ? 1 : 0);
            switch (pattern)
            {
                case 1:
                case 3:
                case 11:
                    xIndex++; // east
                    break;
                case 2:
                case 10:
                case 14:
                    yIndex++; // south
                    break;
                case 8:
                case 12:
                case 13:
                    xIndex--; // west
                    break;
                case 4:
                case 5:
                case 7:
                    yIndex--; // north
                    break;
                default:
                    stop = true; // error: invalid rectangle configuration
                    break;
            }

            // see if (xIndex,yIndex) has arrived back at the starting point
            if ((xIndex == xStartIndex) && (yIndex == yStartIndex))
                stop = true;
        }

        /*
        foreach (Point vertex in vertices)
            DnUtil.Trace("new Point({0},{1}),", vertex.X, vertex.Y);
        */

        return vertices;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Nested Classes
    //

    internal class InvokeHelper
    {
        public DispatcherTimer Timer { get; set; }

        public Action Action { get; set; }

        public void Tick(object sender, EventArgs e)
        {
            Timer.Stop();
            Timer.Tick -= Tick;
            Timer = null;
            Action();
        }
    }

    // unit tests

#if CompileUnitTests
    /// <summary>
    /// Tests <r>RectsToPolygonPoints</r>.
    /// </summary>
    /// 
    /// <exception cref="InvalidOperationException">
    /// A test failed.
    /// </exception>
    ///
    public static void TestRectsToPolygonPoints()
    {
        // test using the example from the comments inside RectsToPolygonPoints()
        var rects = new List<Rect>()
		{
			new Rect(3,1,9,5),
			new Rect(6,3,11,4),
			new Rect(6,7,6,2)
		};
        IList<Point> vertices = RectsToPolygonPoints(rects);
        bool equal = vertices.SequenceEqual(new List<Point>()
		{
            new Point(3,1),
            new Point(12,1),
            new Point(12,3),
            new Point(17,3),
            new Point(17,7),
            new Point(12,7),
            new Point(12,9),
            new Point(6,9),
            new Point(6,6),
            new Point(3,6),
            new Point(3,1)
		});
        if (!equal)
            throw new InvalidOperationException("Failed unit test");

        // test using a more complex example
        rects = new List<Rect>()
        {
            new Rect(1,1,5,3),
            new Rect(4,5,2,3),
            new Rect(3,2,6,4),
            new Rect(2,3,3,3),
            new Rect(3,0,2,1)
        };
        vertices = RectsToPolygonPoints(rects);
        equal = vertices.SequenceEqual(new List<Point>()
        {
            new Point(1,1),
            new Point(3,1),
            new Point(3,0),
            new Point(5,0),
            new Point(5,1),
            new Point(6,1),
            new Point(6,2),
            new Point(9,2),
            new Point(9,6),
            new Point(6,6),
            new Point(6,8),
            new Point(4,8),
            new Point(4,6),
            new Point(2,6),
            new Point(2,4),
            new Point(1,4),
            new Point(1,1)
        });
        if (!equal)
            throw new InvalidOperationException("Failed unit test");

        // test using a single rectangle
        rects = new List<Rect>()
		{
			new Rect(2,3,4,5)
		};
        vertices = RectsToPolygonPoints(rects);
        equal = vertices.SequenceEqual(new List<Point>()
        {
			new Point(2,3),
			new Point(6,3),
			new Point(6,8),
			new Point(2,8),
			new Point(2,3)
		});
        if (!equal)
            throw new InvalidOperationException("Failed unit test");

        // test using two non-adjacent, non-overlapping rectangles -- in this example, only the
        // first will be included in the resulting polygon
        rects = new List<Rect>()
		{
			new Rect(0,0,1,1),
			new Rect(2,2,1,1)
		};
        vertices = RectsToPolygonPoints(rects);
        equal = vertices.SequenceEqual(new List<Point>()
        {
			new Point(0,0),
			new Point(1,0),
			new Point(1,1),
			new Point(0,1),
			new Point(0,0)
		});
        if (!equal)
            throw new InvalidOperationException("Failed unit test");

        // test with a zero-width rectangle
        rects = new List<Rect>()
		{
			new Rect(2,3,0,5)
		};
        vertices = RectsToPolygonPoints(rects);
        equal = vertices.SequenceEqual(new List<Point>()
        {
        });
        if (!equal)
            throw new InvalidOperationException("Failed unit test");

        // test with two adjacent zero-height rectangles
        rects = new List<Rect>()
		{
			new Rect(2,3,5,0),
			new Rect(2,3,3,0)
		};
        vertices = RectsToPolygonPoints(rects);
        equal = vertices.SequenceEqual(new List<Point>()
        {
        });
        if (!equal)
            throw new InvalidOperationException("Failed unit test");

        // test with no rectangles
        rects = new List<Rect>()
        {
        };
        vertices = RectsToPolygonPoints(rects);
        equal = vertices.SequenceEqual(new List<Point>()
        {
        });
        if (!equal)
            throw new InvalidOperationException("Failed unit test");
    }
#endif

}

public static class ControlHelper
{
    /// <summary>
    /// Retrieves the named element in the visual tree of the instantiated control template.
    /// </summary>
    ///
    /// <param name="childName">The name of the element to find.</param>
    /// 
    /// <exception cref="ArgumentException">
    /// The child was not found.
    /// </exception>
    ///
    public static void GetRequiredTemplateChild<T>(IControlHelperConnector connector, string childName, out T result) where T : class
    {
        object resultAsObject = GetRequiredTemplateChild(connector, childName);
        result = resultAsObject as T;
        if (result == null)
            throw NewRequiredTemplateChildIsWrongTypeException(connector, childName, resultAsObject, typeof(T));
    }

    static object GetRequiredTemplateChild(IControlHelperConnector connector, string childName)
    {
        DependencyObject child = connector.GetTemplateChild(childName);
        if (child == null)
        {
            throw new ArgumentException(String.Format(
                "Cannot find element with x:Name=\"{0}\" in the <ControlTemplate> of the <Style> for {1} in Generic.xaml",
                childName, connector.GetControl().GetType()));
        }
        return child;
    }

    static Exception NewRequiredTemplateChildIsWrongTypeException(IControlHelperConnector connector, string childName, object child, Type type)
    {
        return new InvalidOperationException(String.Format(
            "The element with x:Name=\"{0}\" in the <ControlTemplate> of the <Style> for {1} in Generic.xaml is of type {2}; expecting {3}",
            childName, connector.GetControl().GetType(), child.GetType(), type));
    }

    public static void GoToVisualState(IControlHelperConnector connector, string visualStateName, bool useTransitions,
        Action onCompleted)
    {
        VisualState visualState = connector.GetTemplateChild(visualStateName) as VisualState;
        if (visualState == null)
        {
            throw new ArgumentException(
                String.Format("Visual state \"{0}\" not found", visualStateName),
                "visualStateName");
        }

        if (visualState.Storyboard != null)
        {
            new StoryboardCompletedWrapper(visualState.Storyboard, onCompleted);
        }
        else
        {
            connector.GetControl().Dispatcher.BeginInvoke(delegate
            {
                onCompleted();
            });
        }

        VisualStateManager.GoToState(connector.GetControl(), visualStateName, useTransitions);
    }

    class StoryboardCompletedWrapper
    {
        Storyboard _storyboard;
        Action _action;
        public StoryboardCompletedWrapper(Storyboard storyboard, Action action)
        {
            _storyboard = storyboard;
            _action = action;
            _storyboard.Completed += _storyboard_Completed;
        }

        void _storyboard_Completed(object sender, EventArgs e)
        {
            _action();
            _storyboard.Completed -= _storyboard_Completed;
        }
    }
}

/// <summary>
/// TODO
/// </summary>
///
public interface IControlHelperConnector
{
    /// <summary>
    /// TODO
    /// </summary>
    ///
    Control GetControl();

    /// <summary>
    /// TODO
    /// </summary>
    ///
    /// <param name="childName">TODO</param>
    ///
    DependencyObject GetTemplateChild(string childName);
}

/// <summary>
/// Specifies how the visibility of a user interface element is handled.
/// </summary>
///
public enum UiVisibility
{
    /// <summary>
    /// The user interface element is always collapsed, i.e. it takes no space on the screen.
    /// </summary>
    CollapseAlways,

    /// <summary>
    /// The user interface element is collapsed when it is disabled.
    /// </summary>
    CollapseWhenDisabled,

    /// <summary>
    /// The user interface element is hidden (but still occupies space on the screen) when it is disabled.
    /// </summary>
    HideWhenDisabled,

    /// <summary>
    /// The user interface element is always visible, even when it is disabled.
    /// </summary>
    VisibleAlways
}

/// <summary>
/// Performs keyboard operations on an object.
/// </summary>
///
public interface IPerformKeyEvent
{
    /// <summary>
    /// Performs the key-down event on the object.
    /// </summary>
    /// <param name="e">Event arguments.</param>
    ///
    void PerformKeyDown(KeyEventArgs e);

    /// <summary>
    /// Performs the key-up event on the object.
    /// </summary>
    /// <param name="e">Event arguments.</param>
    ///
    void PerformKeyUp(KeyEventArgs e);
}

/// <summary>
/// Accesses the pseudofocus state of an object.  Pseudofocus is a second level of focus, one
/// level deeper than "real focus"; for example, an list box may have focus, and within it one
/// item may have pseudofocus.
/// </summary>
///
public interface IPseudofocusable
{
    /// <summary>
    /// Gets or sets the pseudofocus state of the object.
    /// </summary>
    bool IsPseudofocused { get; set; }
}

/// <summary>
/// Implements helper methods for setting and getting what's called the "item value" of a given
/// <n>FrameworkElement</n>.  For example, in a control like <r>TouchButton</r>, or an item
/// element within a <r>TouchListBox</r>, the "item value" identifies the value to set the
/// associated <r>SyncNode</r> to, or the verb to execute, etc., in the case when the control or
/// element is clicked or selected.  Item value is interpreted in the context of an
/// <r>ItemAction</r> associated with the control.
/// </summary>
/// 
/// <remarks>
/// <para>
/// <r>Item</r> defines the concepts of "untyped value", which is an arbitrary string, and
/// "typed value", which an object of the type determined by the <r>ItemAction</r> associated with
/// the item (in the case of an item such as a <r>TouchButton</r>) or the <r>ItemAction</r>
/// associated with the control containing the item (in the case of an item within a control,
/// such as <r>TouchListBox</r>, which contains a collection of items).
/// </para>
/// <para>
/// An untyped item value can be specified two ways: using the <n>Tag</n> property of the item
/// <n>FrameworkElement</n> (if the static property <r>Item.IsUsingDataProperty</r> is
/// <n>false</n>) or using the <c>c:Item.Data</c> attached property, i.e. the <r cref="DataProperty">Item.Data</r>
/// attached property (if <r>Item.IsUsingDataProperty</r> is <n>true</n>).  For
/// example, if <r>ItemAction</r> for a given <r>TouchButton</r> is
/// <s>ItemAction.SetNumericValue</s>, meaning that clicking the button sets the <r>SyncNode</r>
/// specified by <r>TouchButton.SyncPath</r> to the item value of the <r>TouchButton</r>,
/// the untyped value can be specified either by <c>Tag="1.23"</c> (which is easier to use in
/// Blend, for example) or by <c>c:Item.Data="1.23"</c> (which, in some ways, is a bit "cleaner").
/// In either case, the untyped value is the string "1.23", and, since <r>TouchButton.ItemAction</r>
/// is ItemAction.SetNumericData--TODO??, the typed value is the <n>Double</n> value 1.23.
/// </para>
/// </remarks>
///
public static class Item
{
    /// <summary>
    /// Gets or sets a value indicating whether this application stores item values using the
    /// <c>c:Item.Data</c> attached property (i.e. the <r cref="DataProperty">Item.Data</r>
    /// attached property), in the case where <r>IsUsingDataProperty</r> is <n>false</n>, or
    /// using <n>FrameworkElement.Tag</n>, in the case where <r>IsUsingDataProperty</r> is
    /// <n>true</n>.
    /// </summary>
    public static bool IsUsingDataProperty { get; set; }

    /// <summary>
    /// Identifies the <n>Data</n> attached property.
    /// </summary>
    ///
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.RegisterAttached("Data", typeof(string), typeof(Item),
        null);

    /// <summary>
    /// Gets the value of the <n>Tag</n> or <n>Item.Data</n> property from a specified
    /// <n>FrameworkElement</n>.  Returns <n>null</n> if there is no such value.
    /// Consider using <r>GetUntypedValue</r> instead.
    /// </summary>
    ///
    /// <param name="element">The element from which to get the property value.</param>
    ///
    public static string GetData(FrameworkElement element)
    {
        return (string)element.GetValue(DataProperty);
    }

    /// <summary>
    /// Sets the value of the <n>Item.Data</n> property on a specified
    /// <n>FrameworkElement</n>.  Consider using <r>SetUntypedValue</r> instead.
    /// </summary>
    ///
    /// <param name="element">The element on which to set the property value.</param>
    /// 
    /// <param name="value">The property value.</param>
    ///
    public static void SetData(FrameworkElement element, string value)
    {
        element.SetValue(DataProperty, value);
    }

    /// <summary>
    /// Returns the untyped "item value" of a given <n>FrameworkElement</n>, or <n>null</n> if
    /// there is no such value.
    /// </summary>
    ///
    /// <param name="element">The element from which to get the "item value".</param>
    ///
    public static string GetUntypedValue(FrameworkElement element)
    {
        if (IsUsingDataProperty)
            return (string)element.GetValue(DataProperty);
        else
        if (element.Tag == null)
            return null;
        else
            return element.Tag.ToString(); // Silverlight restricts Tag to strings
    }

    /// <summary>
    /// Sets the untyped "item value" on a given <n>FrameworkElement</n>.
    /// </summary>
    ///
    /// <param name="element">The element on which to set the "item value".</param>
    ///
    /// <param name="value">The untyped item value.  May be <n>null</n>.</param>
    ///
    public static void SetUntypedValue(FrameworkElement element, string value)
    {
        if (IsUsingDataProperty)
            element.SetValue(DataProperty, value);
        else
            element.Tag = value;
    }

    /// <summary>
    /// Gets the value of the <n>Item.Data</n> property from a specified
    /// <n>object</n>.  Returns <n>null</n> if there is no such value.
    /// </summary>
    ///
    /// <param name="item">The object from which to get the property value.</param>
    ///
    public static string GetUntypedValue(object item)
    {
        // NOTE: this method cannot be named GetData, i.e. cannot be a second overload
        // of GetData, since that will cause a "ValidateXaml task failed unexpectedly"
        // error due to System.Reflection.AmbiguousMatchFoundException
        var element = item as FrameworkElement;
        if (element != null)
            return GetUntypedValue(element);
        else
            return null;
    }

    /// <summary>
    /// Returns the item value deserialized from the <r cref="DataProperty">Data</r> attached
	/// property on the
    /// item, or <n>null</n> if there is no Data or if Data doesn't
    /// correspond to a valid item value in the context of a given <r>ItemAction</r>.
    /// </summary>
    /// <param name="itemAction">The <r>ItemAction</r> that defines how to interpret the
    ///     <r cref="Item.DataProperty">Item.Data</r> attached to <pr>item</pr>.</param>
    ///
    /// <param name="item">The item to retrieve the item value of.</param>
    ///
    public static object GetTypedValue(ItemAction itemAction, object item)
    {
        return Item.UntypedValueToTypedValue(itemAction, GetUntypedValue(item));
    }

    /// <summary>
    /// Returns the item value deserialized from the <r cref="DataProperty">Data</r>
	/// attached property on the
    /// item, or <n>null</n> if there is no Data or if Data doesn't
    /// correspond to a valid item value in the context of a given <r>ItemAction</r>.
    /// </summary>
    /// <param name="itemAction">The <r>ItemAction</r> that defines how to interpret the
    ///     <r cref="Item.DataProperty">Item.Data</r> attached to <pr>item</pr>.</param>
    ///
    /// <param name="item">The item to retrieve the item value of.</param>
    ///
    public static object GetTypedValue(ItemAction itemAction, FrameworkElement item)
    {
        return Item.UntypedValueToTypedValue(itemAction, GetUntypedValue(item));
    }

    public static bool GetUntypedValueAndLabel(object item, out string data, out string label)
    {
        TextBlock textBlock = item as TextBlock;
        label = (textBlock != null) ? textBlock.Text : null;
        data = GetUntypedValue(item);
        return (data != null);
    }

    public static bool GetTypedValueAndLabel(ItemAction itemAction, object item, out object itemValue, out string label)
    {
        TextBlock textBlock = item as TextBlock;
        label = (textBlock != null) ? textBlock.Text : null;
        itemValue = Item.GetTypedValue(itemAction, textBlock);
        return (itemValue != null);
    }

    // converts ItemData to the type specified by ItemAction (e.g. converts to a bool if
    // ItemAction is SetBooleanValue); returns null if ItemAction doesn't specify a data type
    // to set SyncNode.Value to on click, or if ItemData isn't convertable to that type,
    // or if ItemData is null
    public static object UntypedValueToTypedValue(ItemAction itemAction, string itemData)
    {
        if (itemData != null)
        {
            try
            {
                switch (itemAction)
                {
                case ItemAction.SetStringValue:
                    return itemData;
                case ItemAction.SetNumericValue:
                    return XmlConvert.ToDouble(itemData);
                case ItemAction.SetBooleanValue:
                    return XmlConvert.ToBoolean(itemData);
                }
            }
            catch (FormatException)
            {
            }
            catch (OverflowException)
            {
            }
        }

        return null;
    }

    public static string TypedValueToUntypedValue(ItemAction itemAction, object value)
    {
        if (value != null)
        {
            try
            {
                switch (itemAction)
                {
                case ItemAction.SetStringValue:
                    return value as string;
                case ItemAction.SetNumericValue:
                    if (value is Double)
                        return XmlConvert.ToString((double)value);
                    break;
                case ItemAction.SetBooleanValue:
                case ItemAction.ToggleBooleanValue:
                    if (value is bool)
                        return XmlConvert.ToString((bool)value);
                    break;
                }
            }
            catch (FormatException)
            {
            }
            catch (OverflowException)
            {
            }
        }

        return null;
    }
}

/// <summary>
/// Helps a control implement synchronization with a server via the global client-side SyncTree.
/// </summary>
///
/// <remarks>
/// To use this class:
/// <list type="bullet">
///     <item><description>
///         Create an instance of <r>SyncHelper</r> for each control that will be bound to the
///         global SyncTree.  The assumption is that the control has a "primary value", i.e. data
/// 		that's displayed in some way that's needs to be kept in sync with a device via the
/// 		server.  <r>SyncNode</r> maintains the primary value of the control, plus an optional
///         secondary <r>SyncNode</r> that holds personalization information (e.g. custom
///         settings, favorites, etc.) and an optional third <r>SyncNode</r> that holds
/// 		configuration information (e.g. a list of possible primary values).
///     </description></item>
///     <item><description>
///         Set <r>SyncPath</r> to the SyncPath corresponding to the primary value of the control.
/// 		If <r>SyncPath</r> is <n>null</n>, the control will not be bound to the global
/// 		SyncTree.
///     </description></item>
///     <item><description>
/// 		Whenever it's possible that the value of the control's <n>Parent</n> property may have
/// 		changed (e.g. whenever the control's <n>LayoutChanged</n> event fires), and once
/// 		initially (e.g. in the control's <n>OnApplyTemplate</n> method), call
/// 		<r>CheckOrphanStatus</r>.  This will prevent memory leaks by removing references from
/// 		the global SyncTree back to this <r>SyncHelper</r> in the case when the control has
/// 		been orphaned.
///     </description></item>
///     <item><description>
///         Set <r>PrimaryNode</r>.<s>SyncNodeBase.Value</s> when the primary value of the control changes -- the value
/// 		will be sent to the server and propagated to other clients automatically.  When
/// 		PrimaryChanged--??TODO fires -- which indicates that the value was changed by another
/// 		client or by some other means, update the control's primary value to the new
/// 		<r>PrimaryNode</r>.<s>SyncNodeBase.Value</s>.
///     </description></item>
///     <item><description>
///         If personalization is used, set <r>PersonalizationNode</r> to information about the
///         personalization of the control, when that information changes -- the personalization
///         will be sent to the server and propagated to other clients automatically.  When
///         <r>PersonalizationChanged</r> fires, update the personalization of the control as
///         specified by the new value of <r>PersonalizationNode</r>.
///     </description></item>
///     <item><description>
///         If configuration is used, read configuration information from <r>ConfigurationNode</r>
///         when <r>ConfigurationChanged</r> fires.
///     </description></item>
///     <item><description>
///         When <r>IsOnlineChanged</r> fires, disable the control if <r>IsOnline</r> is false.
///         This will prevent the user from attempting to change the primary value of the control
/// 		when the connection to the server has been broken.
///     </description></item>
///     <item><description>
///         When <r>IsWaitingChanged</r> fires, display an appropriate "waiting" visualization if
///         <r>IsWaiting</r> is true.  This shows the user that a change they made to the control
///         has not yet been reflected on the server.
///     </description></item>
/// </list>
/// </remarks>
///
public class SyncHelper
{
    //////////////////////////////////////////////////////////////////////////////////////////////
	// Private Fields
	//

    FrameworkElement _owner; // add as parameter to constructor

    /// <summary>
    /// The SyncPath of <r>PrimaryNode</r>, or <n>null</n> if there is no connection currently
	/// between this <r>SyncHelper</r> and the SyncTree of the local client.
    /// </summary>
    ///
    SyncPath _syncPath;

    SyncPath _configurationSyncPath = SyncPathClass.Empty; // same as SyncPath.Build(".")

	/// <summary>
	/// The <r>SyncNode</r> in the SyncTree that corresponds to <r>PrimaryNode</r>, or <n>null</n>
	/// if none.
	/// </summary>
    SyncNode _primaryNode; // the global SyncTree node corresponding to TouchLevel.Level

	/// <summary>
    /// The <r>SyncNode</r> in the SyncTree that corresponds to <r>PersonalizationNode</r>, or
	/// <n>null</n> if none.
	/// </summary>
    SyncNode _personalizationNode;

	/// <summary>
    /// The <r>SyncNode</r> in the SyncTree that corresponds to <r>ConfigurationNode</r>, or
	/// <n>null</n> if none.
	/// </summary>
    SyncNode _configurationNode;

    //////////////////////////////////////////////////////////////////////////////////////////////
	// Public Properties
	//

    /// <summary>
    /// Gets or sets the SyncPath of Value--TODO?, or <n>null</n> if there is no connection
	/// currently between this <r>SyncHelper</r> and the SyncTree of the local client.
    /// </summary>
    public SyncPath SyncPath
    {
        get
        {
            return _syncPath;
        }
        set
        {
            // hook up to the SyncTree, or disconnect from it, depending on <value>
            AdjustConnectionToSyncTree(value, _configurationSyncPath);

            // inform the application of initial value of PrimaryNode
            if (IsPrimaryNodeAvailable)
            {
                if (PrimaryValueChanged != null)
                {
                    PrimaryValueChanged(this, new ValueChangedEventArgs()
                    {
                        NewValue = _primaryNode.Value,
                    });
                }
            }
        }
    }

	// absolute, or relative to SyncPath, or null to indicate that we're currently not using
	// a configuration node
    /// <summary>
    /// TODO
    /// </summary>
    public SyncPath ConfigurationSyncPath
	{
		get
		{
			return _configurationSyncPath;
		}
		set
		{
            // hook up to the configuration node, or disconnect from it, depending on <value>
            AdjustConnectionToSyncTree(_syncPath, value);
		}
	}

    /// <summary>
	/// Returns a value indicating which features of <r>SyncHelper</r> are in use by this instance.
    /// This value is passed into the <r cref="SyncHelper.SyncHelper">SyncHelper constructor</r>.
    /// </summary>
    public SyncHelperFeatures Features { get; private set; }

    /// <summary>
    /// Returns <n>true</n> if a SyncNode is used for configuration.  The SyncPath of the
	/// configuration SyncNode is based on the SyncPath of the primary SyncNode.  If <n>false</n>,
	/// <r>ConfigurationNode</r> cannot be accessed.
    /// </summary>
    public bool IsUsingConfiguration { get; private set; }

    /// <summary>
    /// The primary node that's synchronized with the server.
    /// </summary>
    public SyncNodeBase PrimaryNode
    {
        get
        {
            if (!CheckAccess(0))
				throw new InvalidOperationException("PrimaryNode is not currently accessible");
			return _primaryNode;
        }
    }

    /// <summary>
    /// Returns <n>true</n> if <r>PrimaryNode</r> can be accessed currently.
    /// </summary>
    public bool IsPrimaryNodeAvailable
    {
        get
        {
            return CheckAccess(0);
        }
    }

    /// <summary>
    /// The personalization node (if any) that's synchronized with the server.
    /// </summary>
    public SyncNodeBase PersonalizationNode
    {
        get
        {
            if (!CheckAccess(SyncHelperFeatures.Personalization))
			{
				throw new InvalidOperationException(
					"PersonalizationNode is not currently accessible");
			}
			return _personalizationNode;
        }
    }

    /// <summary>
    /// Returns <n>true</n> if <r>PersonalizationNode</r> can be accessed currently.
    /// </summary>
    public bool IsPersonalizationNodeAvailable
    {
        get
        {
            return CheckAccess(SyncHelperFeatures.Personalization);
        }
    }

    /// <summary>
    /// The configuration node (if any) that's synchronized with the server.
    /// </summary>
    public SyncNodeBase ConfigurationNode
    {
        get
        {
            if (!CheckAccess(SyncHelperFeatures.Configuration))
			{
				throw new InvalidOperationException(
					"ConfigurationNode is not currently accessible");
			}
			return _configurationNode;
        }
    }

    /// <summary>
    /// Returns <n>true</n> if <r>ConfigurationNode</r> can be accessed currently.
    /// </summary>
    public bool IsConfigurationNodeAvailable
    {
        get
        {
            return CheckAccess(SyncHelperFeatures.Configuration);
        }
    }

    /// <summary>
    /// Returns <n>true</n> if SyncTree is currently online, i.e. if there is a network connection
	/// to the server, and the value of <r>PrimaryNode</r> is non-null.
    /// </summary>
    public bool IsOnline { get; private set; }

    /// <summary>
    /// Returns <n>true</n> if a value written to <r>PrimaryNode</r>.<s>SyncNodeBase.Value</s>,
    /// <r>PersonalizationNode</r>.<s>SyncNodeBase.Value</s>, or <r>ConfigurationNode</r>.<s>SyncNodeBase.Value</s> has not yet been
	/// reflected on the server.
    /// </summary>
    public bool IsWaiting { get; private set; }

    /// <summary>
    /// Returns <n>true</n> if the control associated with this <r>SyncHelper</r> no longer has a
	/// parent, <n>false</n> if the control has a parent.  Has no value if the parentage is
	/// unknown.
    /// </summary>
    public bool? IsOrphan { get; private set; }

    //////////////////////////////////////////////////////////////////////////////////////////////
	// Public Events
	//

    /// <summary>
    /// Fired when <r>PrimaryNode</r>.<s>SyncNodeBase.Value</s> changes.
    /// </summary>
    public event ValueChangedEventHandler PrimaryValueChanged;

    /// <summary>
    /// Fired when a descendant of <r>PrimaryNode</r> changes.  May only be used if
    /// <r>Features</r> includes <r>SyncHelperFeatures.PrimaryDescendantChanged</r>.
    /// </summary>
    public event DescendantChangedEventHandler PrimaryDescendantChanged;

    /// <summary>
    /// Fired when a descendant of <r>PersonalizationNode</r> changes.  May only be used if
    /// <r>Features</r> includes <r>SyncHelperFeatures.Personalization</r>.
    /// </summary>
    public event DescendantChangedEventHandler PersonalizationChanged;

    /// <summary>
    /// Fired when a descendant of <r>ConfigurationNode</r> changes.  May only be used if
    /// <r>Features</r> includes <r>SyncHelperFeatures.Configuration</r>.
    /// </summary>
    public event DescendantChangedEventHandler ConfigurationChanged;

    /// <summary>
    /// Fired when <r>IsOnline</r> changes.
    /// </summary>
    public event EventHandler IsOnlineChanged;

    /// <summary>
    /// Fired when <r>IsWaiting</r> changes.
    /// </summary>
    public event EventHandler IsWaitingChanged;

    //////////////////////////////////////////////////////////////////////////////////////////////
	// Public Methods
	//

    /// <summary>
    /// Initializes an instance of this class.
    /// </summary>
    ///
    /// <param name="owner">TODO</param>
    ///
    /// <param name="features">Specifies the features of <r>SyncHelper</r> that are to be made
	/// 	available.</param>
    ///
	public SyncHelper(FrameworkElement owner, SyncHelperFeatures features)
	{
        _owner = owner;
		Features = features;
	}

	/// <summary>
	/// Checks to see if <r>IsOrphan</r> needs to change.
	/// </summary>
    /// 
    /// <param name="parent">TODO</param>
	///
	/// <remarks>
	/// Whenever it's possible that the value of the control's <n>Parent</n> property may have
	/// changed (e.g. whenever the control's <n>LayoutChanged</n> event fires), and once
	/// initially (e.g. in the control's <n>OnApplyTemplate</n> method), the application should
	/// call <r>CheckOrphanStatus</r>.  This will prevent memory leaks by removing references from
	/// the global SyncTree back to this <r>SyncHelper</r> in the case when the control has
	/// been orphaned.
	/// </remarks>
	///
    public void CheckOrphanStatus(DependencyObject parent)
    {
        bool isNowOrphan = (parent == null);
        if (!IsOrphan.HasValue || (IsOrphan.Value != isNowOrphan))
        {
            IsOrphan = isNowOrphan;
            if (isNowOrphan)
            {
                // control was just orphaned, i.e. removed from its parent -- remove references
                // from the SyncTree (if any) to allow this control to be garbage-collected
                AdjustConnectionToSyncTree(_syncPath, _configurationSyncPath);
            }
        }
    }

    /// <summary>
    /// Performs a specified <r>ItemAction</r> on <r>PrimaryNode</r>.
    /// </summary>
    ///
    /// <param name="itemAction">The <r>ItemAction</r> to perform.</param>
    ///
    /// <param name="itemData">Data to provide the click action.</param>
	///
    /// <param name="originator">The object that originated the action, or <n>null</n> if none.
	/// 	</param>
    ///
    public void PerformItemAction(ItemAction itemAction, string itemData,
        object originator)
	{
		if (_primaryNode != null)
            _primaryNode.PerformItemAction(itemAction, itemData, originator);
	}

    //////////////////////////////////////////////////////////////////////////////////////////////
	// Private Methods
	//

    /// <summary>
	/// If <pr>syncPath</pr> is different than <r>SyncPath</r>, this method disconnects this
	/// <r>SyncHelper</r> from the global SyncTree, updates the value of <r>SyncPath</r> and/or
	/// <r>ConfigurationSyncPath</r>, and then, if <r>SyncPath</r> is not <n>null</n>, reconnects
	/// this <r>SyncHelper</r> to the global SyncTree.  If <pr>syncPath</pr> is <n>null</n>, this
	/// method just updates the connection to the configuratio node.
    /// </summary>
    ///
    /// <param name="syncPath">The new value for <r>SyncPath</r>.</param>
	///
    /// <param name="configurationSyncPath">The new value for <r>ConfigurationSyncPath</r>.</param>
    ///
    void AdjustConnectionToSyncTree(SyncPath syncPath, SyncPath configurationSyncPath)
    {
        bool isOrphan = IsOrphan.HasValue && IsOrphan.Value;
		bool changingSyncPath = (syncPath != _syncPath);
		bool changingConfigurationSyncPath = (configurationSyncPath != _configurationSyncPath);
        ISyncClient syncClient = ClientGlobals.GetSyncClient(_owner);
        SyncNodeBase syncTree = syncClient.SyncTree;

		if (changingSyncPath || isOrphan)
		{
            // remove references from the global SyncTree to <_primaryNode>, and set <_primaryNode> to
			// null, if it's not already null; do the same for IsOnlineChanged
			if (_primaryNode != null)
			{
				_primaryNode.ValueChanged -= OnPrimaryNodeValueChanged;
				if ((Features & SyncHelperFeatures.PrimaryDescendantChanged) != 0)
					_primaryNode.DescendantChanged -= OnPrimaryNodeDescendantChanged;
				_primaryNode.IsWaitingChanged -= OnIsWaitingChanged;
				syncClient.IsOnlineChanged -= OnIsOnlineChanged;
				_primaryNode = null;
			}

			// remove references from the global SyncTree to <_personalizationNode>, and set
			// <_personalizationNode> to null, if it's not already null
			if (_personalizationNode != null)
			{
				//_personalizationNode.ValueChanged -= OnPersonalizationNodeValueChanged;
				_personalizationNode.DescendantChanged -= OnPersonalizationNodeDescendantChanged;
				_personalizationNode.IsWaitingChanged -= OnIsWaitingChanged;
				_personalizationNode = null;
			}
		}

        if (changingConfigurationSyncPath || changingSyncPath || isOrphan)
		{
			// remove references from the global SyncTree to <_configurationNode>, and set
			// <_configurationNode> to null, if it's not already null
			if (_configurationNode != null)
			{
				//_configurationNode.ValueChanged -= OnConfigurationNodeValueChanged;
				_configurationNode.DescendantChanged -= OnConfigurationNodeDescendantChanged;
				_configurationNode.IsWaitingChanged -= OnIsWaitingChanged;
				_configurationNode = null;
			}
		}

        // update <_syncPath> and <_configurationSyncPath>
        _syncPath = syncPath;
        _configurationSyncPath = configurationSyncPath;

        // if we should reconnect with the global SyncTree, do so now
        if (changingSyncPath && (syncPath != null) && !isOrphan)
        {
            // reconnect <_primaryNode>
            _primaryNode = (SyncNode) syncTree.CreateNode(syncPath);
            _primaryNode.ValueChanged += OnPrimaryNodeValueChanged;
            if ((Features & SyncHelperFeatures.PrimaryDescendantChanged) != 0)
                _primaryNode.DescendantChanged += OnPrimaryNodeDescendantChanged;
            _primaryNode.IsWaitingChanged += OnIsWaitingChanged;

            // reconnect <_personalizationNode>, if the application is using personalization
            if ((Features & SyncHelperFeatures.Personalization) != 0)
            {
                _personalizationNode = (SyncNode) syncTree.CreateNode(SyncPathClass.Build(
                    "Personalization", syncPath));
                //_personalizationNode.ValueChanged += OnPersonalizationNodeValueChanged;
                _personalizationNode.DescendantChanged += OnPersonalizationNodeDescendantChanged;
                _personalizationNode.IsWaitingChanged += OnIsWaitingChanged;
            }

            // keep track of whether the sync tree is online
            syncClient.IsOnlineChanged += OnIsOnlineChanged;
            UpdateOnlineStatus(true);
        }

		// reconnect <_configurationNode>, if the application is using configuration
        if ((changingSyncPath || changingConfigurationSyncPath) &&
            (configurationSyncPath != null) && (syncPath != null) &&
		    ((Features & SyncHelperFeatures.Configuration) != 0) && !isOrphan)
		{
			_configurationNode = (SyncNode) syncTree.CreateNode(SyncPathClass.Build(
				"Configuration", syncPath, configurationSyncPath));
			//_configurationNode.ValueChanged += OnConfigurationNodeValueChanged;
			_configurationNode.DescendantChanged += OnConfigurationNodeDescendantChanged;
			_configurationNode.IsWaitingChanged += OnIsWaitingChanged;
        }
    }

    /// <summary>
    /// Called when <r>_primaryNode</r><s>SyncNode.Value</s> has changed.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void OnPrimaryNodeValueChanged(object sender, ValueChangedEventArgs e)
    {
        // notify the application of the event
        if (PrimaryValueChanged != null)
        {
            try
            {
                PrimaryValueChanged(this, e);
            }
            catch (InvalidOperationException ex)
            {
                ClientGlobals.Alert(AlertType.Warning,
                    "{0}: Rejected change of \"{1}\" value to \"{2}\": {3}",
                    ClientUtil.GetLabel(_owner), PrimaryNode.Path, e.NewValue, ex.Message);
            }
        }

        // if <Primary.Value> value changed to/from null and SyncClient is currently online,
		// <IsOnline> changes, since <IsOnline> is false when the value of <_primaryNode> is null
        ISyncClient syncClient = ClientGlobals.GetSyncClient(_owner);
        if (syncClient.IsOnline && ((e.OldValue == null) != (e.NewValue == null)))
            UpdateOnlineStatus(false);
    }

    /// <summary>
    /// Called when a descendant of <r>_primaryNode</r> has changed.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void OnPrimaryNodeDescendantChanged(object sender, DescendantChangedEventArgs e)
    {
        // notify the application of the event
        if (PrimaryDescendantChanged != null)
            PrimaryDescendantChanged(sender, e);
    }

    /// <summary>
    /// Called when a descendant of <r>_personalizationNode</r> has changed.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void OnPersonalizationNodeDescendantChanged(object sender, DescendantChangedEventArgs e)
    {
        // notify the application of the event
        if (PersonalizationChanged != null)
            PersonalizationChanged(sender, e);
    }

    /// <summary>
    /// Called when a descendant of <r>_configurationNode</r> has changed.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void OnConfigurationNodeDescendantChanged(object sender, DescendantChangedEventArgs e)
    {
        // notify the application of the event
        if (ConfigurationChanged != null)
            ConfigurationChanged(sender, e);
    }

    /// <summary>
    /// Called when <s>SyncNode.IsWaiting</s> changed on <r>_primaryNode</r>, <r>_personalizationNode</r>, or
	/// <r>_configurationNode</r> changed.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void OnIsWaitingChanged(object sender, IsWaitingChangedEventArgs e)
    {
        bool newIsWaiting = _primaryNode.IsWaiting;
        if (_personalizationNode != null)
            newIsWaiting = newIsWaiting || _personalizationNode.IsWaiting;
        if (_configurationNode != null)
            newIsWaiting = newIsWaiting || _configurationNode.IsWaiting;
        if (IsWaiting != newIsWaiting)
        {
            IsWaiting = newIsWaiting;
            if (IsWaitingChanged != null)
                IsWaitingChanged(sender, EventArgs.Empty);
        }
    }

    /// <summary>
    /// Called when <r>_primaryNode</r>.<s>SyncHelper.IsOnline</s> has changed.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void OnIsOnlineChanged(object sender, IsOnlineChangedEventArgs e)
    {
        UpdateOnlineStatus(false);
    }

	/// <summary>
	/// Updates <r>IsOnline</r> based on the online status of the SyncTree and whether
	/// <n>PrimaryNode.Value</n> is <n>null</n>.
	/// </summary>
    /// 
    /// <param name="forceEvent">If <n>true</n>, the <r>IsOnlineChanged</r> event is fired even if
    ///     <r>IsOnline</r> hasn't changed.</param>
    ///
    /// <remarks>
    /// Note that in design mode we "lie" and say that we're always online.  Otherwise, in design
    /// tools, controls will appear always to be disabled.
    /// </remarks>
    ///
    void UpdateOnlineStatus(bool forceEvent)
    {
        // set <newIsOnline> to indicate our current online status
        bool newIsOnline;
        if (DesignerProperties.IsInDesignTool)
            newIsOnline = true;
        else
        {
            ISyncClient syncClient = ClientGlobals.GetSyncClient(_owner);
            newIsOnline = syncClient.IsOnline && (_primaryNode.Value != null);
        }

        // as appropriate, update IsOnline and fire IsOnlineChanged
        if ((IsOnline != newIsOnline) || forceEvent)
        {
            IsOnline = newIsOnline;
            if (IsOnlineChanged != null)
                IsOnlineChanged(this, EventArgs.Empty);
        }
    }

    /// <summary>
    /// Returns <n>true</n> if this <r>SyncHelper</r> is in a state where <r>SyncNode</r> values
	/// can be accessed, <n>false</n> otherwise.
    /// </summary>
	///
	/// <param name="features">Additional features to check.  This method returns <n>false</n> if
	/// 	any of these features are not available.</param>
    ///
    public bool CheckAccess(SyncHelperFeatures features)
    {
        if (_syncPath == null)
            return false;
		if ((features & Features) != features)
			return false;
        ISyncClient syncClient = ClientGlobals.GetSyncClient(_owner);
        if (!syncClient.IsOnline)
            return false;
        if (IsOrphan.HasValue && IsOrphan.Value)
            return false;
        return true;
    }
}

/// <summary>
/// Specifies which features are in use for a given <r>SyncHelper</r> instance.
/// </summary>
[Flags]
public enum SyncHelperFeatures
{
    /// <summary>
    /// <r>SyncHelper.PersonalizationNode</r> is used.
    /// </summary>
    Personalization = 0x01,

    /// <summary>
    /// <r>SyncHelper.ConfigurationNode</r> is used.
    /// </summary>
    Configuration = 0x02,

    /// <summary>
    /// <r>SyncHelper.PrimaryDescendantChanged</r> is used.
    /// </summary>
    PrimaryDescendantChanged = 0x04,
}

}
